﻿namespace CatchBall
{
    using System;
    using System.Collections.Generic;
    using System.IO;
    using System.Threading;

    public class Scores
    {
        public const char ScoreSeparator = ',';
        private static byte numHighscoresRemembered = 5; // TODO: why it is = 5? if it must be five then the field must be a constant (const)
        private static string scoresFile = "../../External Files/highscores.txt";
        private readonly List<string> highscores;
        
        public Scores(string level)
        {
            switch (level)
            {
                case "1": scoresFile = "../../External Files/highscores1.txt";
                    break;
                case "2": scoresFile = "../../External Files/highscores2.txt";
                    break;
                case "3": scoresFile = "../../External Files/highscores3.txt";
                    break;
                case "4": scoresFile = "../../External Files/highscores4.txt";
                    break;
                case "5": scoresFile = "../../External Files/highscores5.txt";
                    break;
            }

            this.highscores = ReadHighScoresFromFile();
        }

        public void PrintHighscore(int playerScore, int playerPosition)
        {
            Console.Clear();
            var row = 13;
            Console.SetCursorPosition(23, 11);
            Console.WriteLine("High scores:");

            for (var i = 0; i < Math.Min(numHighscoresRemembered * 2, this.highscores.Count); i += 2)
            {
                if (i == playerPosition)
                {
                    Console.ForegroundColor = ConsoleColor.Green;
                }

                Console.SetCursorPosition(23, row);
                Console.Write(((i / 2) + 1) + ". " + this.highscores[i + 1] + ": " + this.highscores[i] + " points");
                row++;
                Console.ResetColor();
            }
        }

        // congratulates you if in top 10, updates the records, prints the score
        public void VictoryCeremony(int score)
        {
            var position = this.ReturnPositonToInsert(score);
            Console.WriteLine(position + " " + (this.highscores.Count - 1));
            if (position != (-1))
            {
                Console.SetCursorPosition(10, 13);
                Console.WriteLine("Congratulations! You finished {0}th", (position / 2) + 1);
                this.ChangeHighscore(score, position);
                this.PrintHighscore(score, position);
                this.WriteNewHighscoreToFile(this.highscores);
            }
            else
            {
                Console.SetCursorPosition(10, 13);
                Console.WriteLine("Great Job! Unfortunately you didn't make it to the top {0}", numHighscoresRemembered);
                Thread.Sleep(3000);
                this.PrintHighscore(score, position);
            }
        }

        // doesn't let you enter the score separator in the name wich causes an exception
        private static bool IncorrectName(string name)
        {
            return name.Contains(ScoreSeparator.ToString()) || name.Contains(" ");
        }

        private static string EnterPlayerName()
        {
            Console.WriteLine("Enter your name: ");
            Console.SetCursorPosition(23, 15);
            Console.CursorVisible = true;
            var playerName = Console.ReadLine();

            // prevent entering the score separator in the user name
            while (IncorrectName(playerName))
            {
                Console.WriteLine("Entered invalid name, please exclude {0} from it", ScoreSeparator);
                Console.SetCursorPosition(23, 15);
                Console.CursorVisible = true;
                playerName = Console.ReadLine();
            }

            // validation check for NullReferenceException
            if (playerName != null)
            {
                playerName = playerName.Trim();
            }
            
            Console.CursorVisible = false;
            Console.Clear();
            return playerName;
        }

        // load highscores from file and convert them to list of alternating scores and player names
        private static List<string> ReadHighScoresFromFile()
        {
            var highScores = new string[] {};

            try
            {
                using (var reader = new StreamReader(scoresFile))
                {
                    var highScoresFile = reader.ReadToEnd();
                    highScores = highScoresFile.Split(new char[] {ScoreSeparator}, StringSplitOptions.RemoveEmptyEntries);
                }
            }
            catch (FileNotFoundException exception)
            {
                return new List<string>();
            }
            catch (IOException)
            {
                return new List<string>();
            }

            var highscoresList = new List<string>(highScores);
            return highscoresList;
        }

        // write the scores to file
        private void WriteNewHighscoreToFile(List<string> newHighScore) // TODO: never used newHighScore
        {
            using (var writer = new StreamWriter(scoresFile, false))
            {
                var result = string.Join(ScoreSeparator.ToString(), this.highscores);
                writer.Write(result);
            }
        }

        // updates highscore with the player's score at a given position
        private void ChangeHighscore(int playerScore, int position)
        {
            var playerName = EnterPlayerName();

            // insert position
            if (position < this.highscores.Count)
            {
                this.highscores.Insert(position, playerScore.ToString());
                this.highscores.Insert(position + 1, playerName);
            }
            else if (position <= numHighscoresRemembered * 2)
            {
                this.highscores.Add(playerScore.ToString());
                this.highscores.Add(playerName);
            }

            // delete last scores only if more than the number we remember
            if (this.highscores.Count > numHighscoresRemembered * 2)
            {
                this.highscores.RemoveAt(this.highscores.Count - 1);
                this.highscores.RemoveAt(this.highscores.Count - 1);
            }
        }

        // finds player's rank
        private int ReturnPositonToInsert(int playerScore)
        {
            // look through the recorded positions
            for (var i = 0; i < this.highscores.Count; i += 2)
            {
                // TODO: we can do it with binary search
                if (playerScore >= int.Parse(this.highscores[i]))
                {
                    return i;
                }
            }

            // return the last available position if there are not enough records to fill in numHighscoresRemembered scores
            if (this.highscores.Count < numHighscoresRemembered * 2)
            {
                return this.highscores.Count;
            }

            return -1;
        }
    }
}